package com.zhl.study.configuration;

import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.dynamic.datasource.provider.AbstractDataSourceProvider;
import com.baomidou.dynamic.datasource.provider.DynamicDataSourceProvider;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.plugins.SqlExplainInterceptor;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.util.*;

import static com.alibaba.druid.pool.DruidDataSourceFactory.createDataSource;

@Configuration
public class DatasourceConfiguration {

    private static final Logger LOGGER = LoggerFactory.getLogger(DatasourceConfiguration.class);

    @Autowired
    private DynamicDataSourceProperties properties;

    @Bean
    public DynamicDataSourceProvider dynamicDataSourceProvider() {
        return new AbstractDataSourceProvider() {
            @Override
            public Map<String, DataSource> loadDataSources() {
                Map<String, DataSourceProperty> dataSourcePropertiesMap = properties.getDatasource();
                return createDataSourceMap(dataSourcePropertiesMap);
            }
        };
    }

    @Bean
    @Primary
    public DataSource dataSource() {
        DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource();
        dataSource.setPrimary(properties.getPrimary());
        dataSource.setStrict(properties.getStrict());
        dataSource.setStrategy(properties.getStrategy());
        dataSource.setP6spy(properties.getP6spy());
        dataSource.setSeata(properties.getSeata());

        Map<String, DataSource> dataSourceMap = new HashMap<>();
        Map<String, DataSourceProperty> datasourceMap = properties.getDatasource();
        datasourceMap.forEach((key, property) -> {
            DataSource ds = createDataSource(property);
            dataSourceMap.put(key, ds);
        });

        // 设置数据源
//        dataSource.setDataSources(dataSourceMap);

        return dataSource;
    }

    private DataSource createDataSource(DataSourceProperty property) {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl(property.getUrl());
        dataSource.setUsername(property.getUsername());
        dataSource.setPassword(property.getPassword());
        dataSource.setDriverClassName(property.getDriverClassName());
        return dataSource;
    }

    @Bean
    public DataSource masterWriteDataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        DataSourceProperty dataSourceProperty = properties.getDatasource().get("master-write");
        if (dataSourceProperty != null) {
            dataSource.setUrl(dataSourceProperty.getUrl());
            dataSource.setUsername(dataSourceProperty.getUsername());
            dataSource.setPassword(dataSourceProperty.getPassword());
            dataSource.setDriverClassName(dataSourceProperty.getDriverClassName());
        }
        return dataSource;
    }

    @Bean
    public DataSource masterReadDataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        DataSourceProperty dataSourceProperty = properties.getDatasource().get("master-read");
        if (dataSourceProperty != null) {
            dataSource.setUrl(dataSourceProperty.getUrl());
            dataSource.setUsername(dataSourceProperty.getUsername());
            dataSource.setPassword(dataSourceProperty.getPassword());
            dataSource.setDriverClassName(dataSourceProperty.getDriverClassName());
        }
        return dataSource;
    }

    // 自定义切面，用于读写分离
    @Bean
    public DataSourceAspect dataSourceAspect() {
        return new DataSourceAspect();
    }

    /**
     * 分页插件
     *
     * @return {@link PaginationInterceptor}
     */
    @Bean
    @ConditionalOnMissingBean(value = PaginationInterceptor.class)
    public PaginationInterceptor paginationInterceptor() {
        LOGGER.info("mybatis plus pagination interceptor init success !!!");
        return new PaginationInterceptor();
    }


    /**
     * 数据源切面类
     *
     * 1.对系统的mapper层进行切面
     * 2.如果方法名使用以下字符开头使用读数据源
     * {@code query} {@code find} {@code get} {@code select} {@code exist} {@code is}
     *
     * @author asheng
     * @since 2019/10/16
     */
    @Aspect
    @Component
    public static class DataSourceAspect {

        @Before("execution(* com..service..*.*(..))")
        public void before(JoinPoint point) {
            String methodName = point.getSignature().getName();
            if (isQuery(methodName)) {
                DataSourceHolder.setDataSourceType(DataSourceType.READER);
            } else {
                DataSourceHolder.setDataSourceType(DataSourceType.WRITER);
            }
        }

        @Before("@annotation(ds)")
        public void annotationBefore(JoinPoint point, DS ds) {
            DataSourceType dataSourceType = DataSourceType.valueOf(ds.value().toUpperCase());
            DataSourceHolder.setDataSourceType(dataSourceType);
        }

        @After("execution(* com..service..*.*(..))")
        public void after() {
            DataSourceHolder.clear();
        }

        /**
         * 判断切面的方法是否是查询方法
         *
         * @param methodName 方法名
         * @return true: 是查询方法  false: 不是查询方法
         */
        private Boolean isQuery(String methodName) {
            return StringUtils.startsWithAny(methodName, new String[]{"query", "find", "get", "select", "exist", "is", "list"});
        }
    }

    /**
     * 动态数据源，决定使用哪个数据源
     *
     * @author asheng
     * @since 2019/10/16
     */
    private static class DynamicDataSource extends AbstractRoutingDataSource {

        @Override
        protected Object determineCurrentLookupKey() {
            return DataSourceHolder.getDataSourceType();
        }
    }

    /**
     * 不同线程对数据源类型的持有
     *
     * @author asheng
     * @since 2019/10/16
     */
    private static class DataSourceHolder {

        private static final ThreadLocal<DataSourceType> HOLDER = new ThreadLocal<>();

        static void setDataSourceType(DataSourceType type) {
            HOLDER.set(type);
        }

        static DataSourceType getDataSourceType() {
            return HOLDER.get();
        }

        private static void clear() {
            HOLDER.remove();
        }
    }

    /**
     * 数据源类型
     *
     * @author asheng
     * @since 2019/10/16
     */
    private enum DataSourceType {
        /** 写数据源 */
        WRITER,
        /** 读数据源 */
        READER,
        /** doris **/
        DORIS
    }

    @Bean
    public GlobalConfig globalConfig() {
        GlobalConfig globalConfig = new GlobalConfig();
        globalConfig.setMetaObjectHandler(new MyMetaObjectHandler());
        return globalConfig;
    }

    @Bean
    public SqlExplainInterceptor sqlExplainInterceptor() {
        SqlExplainInterceptor sqlExplainInterceptor = new SqlExplainInterceptor();
        //格式化sql语句
        Properties properties = new Properties();
        properties.setProperty("format", "false");
        sqlExplainInterceptor.setProperties(properties);
        return sqlExplainInterceptor;
    }
}

